/*
 * Galaxium Messenger
 * Copyright (C) 2007 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.Text;

using Anculus.Core;

using Galaxium.Core;

namespace Galaxium.Protocol.Msn
{
	public static class MsnEncodingUtility
	{
		static string urlAllowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz$-_.+!*'(),;/?:@=&";
		
		public static string UrlEncode (string str, Encoding enc)
		{
			string result = string.Empty;
			
			foreach (char ch in str)
			{
				if (urlAllowedChars.IndexOf (ch) >= 0)
					result += ch;
				else
				{
					byte[] bytes = enc.GetBytes (new char[] { ch });
					
					foreach (byte b in bytes)
						result += string.Format ("%{0:x2}", b);
				}
			}
			
			return result;
		}
		
		
		public static string UrlEncode (string str)
		{
			return UrlEncode (str, Encoding.UTF8);
		}
		
		public static string UrlDecode (string str, Encoding enc, char prefix)
		{
			string result = string.Empty;
			
			while (str.Length > 0)
			{
				if (str[0] == prefix)
				{
					List<byte> bytes = new List<byte> ();
					
					while ((str.Length > 0) && (str[0] == prefix))
					{
						str = str.Substring (1);

						try
						{
							byte val = byte.Parse (str.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
							bytes.Add (val);
							
							str = str.Substring (2);
						}
						catch (Exception)
						{
							throw new ApplicationException ("Invalid URL Encoded String, % not followed by 2 digits");
						}
					}
					
					try
					{
						result += new string (enc.GetChars (bytes.ToArray ()));
					}
					catch (Exception)
					{
						string bytestr = string.Empty;
						
						foreach (byte b in bytes)
							bytestr += string.Format ("0x{0:00} ", b);
						
						throw new ApplicationException (string.Format ("Invalid URL Encoded String, bytes not valid for encoding\n{0}", bytestr));
					}
				}
				else
				{
					result += str[0];
					str = str.Substring (1);
				}
			}
			
			return result;
		}
		
		public static string UrlDecode (string str, Encoding enc)
		{
			return UrlDecode (str, enc, '%');
		}
		
		public static string UrlDecode (string str)
		{
			return UrlDecode (str, Encoding.UTF8);
		}
		
		public static string UrlDecodeSafe (string str, Encoding enc)
		{
			try
			{
				return UrlDecode (str, enc);
			}
			catch (Exception)
			{
				return str;
			}
		}
		
		public static string UrlDecodeSafe (string str)
		{
			return UrlDecodeSafe (str, Encoding.UTF8);
		}
		
		public static string Base64Encode (string str, Encoding enc)
		{
			return Convert.ToBase64String (enc.GetBytes (str));
		}
		
		public static string Base64Encode (string str)
		{
			return Base64Encode (str, Encoding.UTF8);
		}
		
		public static string Base64Decode (string str, Encoding enc)
		{
			Base64Decoder dec = new Base64Decoder (str);
			return enc.GetString (dec.GetDecoded ());
		}
		
		public static string Base64DecodeSafe (string str, Encoding enc)
		{
			try
			{
				return Base64Decode (str, enc);
			}
			catch
			{
				return str;
			}
		}
		
		public static string Base64Decode (string str)
		{
			return Base64Decode (str, Encoding.UTF8);
		}
		
		public static string Base64DecodeSafe (string str)
		{
			try
			{
				return Base64Decode (str);
			}
			catch
			{
				return str;
			}
		}
		
		public static string DecodeMailField (string str)
		{
			string[] chunks = str.Split (new char[] { '?' }, StringSplitOptions.RemoveEmptyEntries);
			Encoding enc = Encoding.UTF8;
			
			try
			{
				enc = Encoding.GetEncoding (chunks[1]);
			}
			catch (ArgumentException)
			{
				Log.Warn ("Unable to find encoding {0}", chunks[1]);
			}
			
			if (chunks[2] == "B")
				return Base64Decode (chunks[3], enc);
			else
				return UrlDecode (chunks[3], enc, '=');
		}
		
		public static string EncodeMailField (string str)
		{
			return string.Format ("=?utf-8?B?{0}?=", Base64Encode (str, Encoding.UTF8));
		}
	}
	
	// Convert.FromBase64String seems to fail with KMess MsnObjects for some reason
	// The code below doesn't, although somewhere the closing "/>" gets cut off
	// I don't know if thats KMess' fault or ours...
	// 
	// Based on http://lists.ximian.com/pipermail/mono-list/2006-March/031023.html
	public class Base64Decoder
	{
		char[] source;
		int length1;
		int blockCount;
		int paddingCount;
		
		public Base64Decoder (string input)
		{
			input = BaseUtility.ReplaceString (input, " ", "", true);
			input = BaseUtility.ReplaceString (input, "\r", "", true);
			input = BaseUtility.ReplaceString (input, "\n", "", true);
			
			source = input.ToCharArray ();
			
			for (int i = 0; i < 2; i++)
			{
				if (source[source.Length - i - 1] == '=')
					paddingCount++;
			}
			
			blockCount = source.Length / 4;
			length1 = blockCount * 3;
		}
		
		public byte[] GetDecoded ()
		{
			byte[] buffer = new byte[source.Length];
			byte[] buffer2 = new byte[length1];
			
			for (int x = 0; x < source.Length; x++)
				buffer[x] = ToSixBit (source[x]);
			
			byte b, b1, b2, b3;
			byte temp1, temp2, temp3, temp4;
			
			for (int x = 0; x < blockCount; x++)
			{
				temp1 = buffer[x * 4];
				temp2 = buffer[x * 4 + 1];
				temp3 = buffer[x * 4 + 2];
				temp4 = buffer[x * 4 + 3];
				
				b = (byte)(temp1 << 2);
				b1 = (byte)((temp2 & 48) >> 4);
				b1 += b;
				
				b = (byte)((temp2 & 15) << 4);
				b2 = (byte)((temp3 & 60) >> 2);
				b2 += b;
				
				b = (byte)((temp3 & 3) << 6);
				b3 = temp4;
				b3 += b;
				
				buffer2[x * 3] = b1;
				buffer2[x * 3 + 1] = b2;
				buffer2[x * 3 + 2] = b3;
			}
			
			// Remove padding
			byte[] result = new byte[length1 - paddingCount];
			Array.Copy (buffer2, result, result.Length);

			return result;
		}
		
		private byte ToSixBit (char c)
		{
			char[] lookupTable = new char[64]
			{
				'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
				'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
				'0','1','2','3','4','5','6','7','8','9','+','/'
			};

			for (byte x = 0; x < 64; x++)
			{
				if (lookupTable[x] == c)
					return x;
			}
				
			return 0;
		}
	}
}
